/******************************************************************************
 * Copyright (c) 2022 Telink Semiconductor (Shanghai) Co., Ltd. ("TELINK")
 * All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *****************************************************************************/
#include "analog.h"
#include "compiler.h"
#include "plic.h"
#include "stimer.h"
/**********************************************************************************************************************
 *                                			  local constants                                                       *
 *********************************************************************************************************************/

/**********************************************************************************************************************
 *                                           	local macro                                                        *
 *********************************************************************************************************************/

/**********************************************************************************************************************
 *                                             local data type                                                     *
 *********************************************************************************************************************/

/**********************************************************************************************************************
 *                                              global variable                                                       *
 *********************************************************************************************************************/

dma_config_t analog_tx_dma_config = {
    .dst_req_sel = DMA_REQ_ALGM_TX, /* < tx req  */
    .src_req_sel = 0,
    .dst_addr_ctrl = DMA_ADDR_FIX,
    .src_addr_ctrl = DMA_ADDR_INCREMENT, /* < increment */
    .dstmode = DMA_HANDSHAKE_MODE,       /* < handshake */
    .srcmode = DMA_NORMAL_MODE,
    .dstwidth = DMA_CTR_WORD_WIDTH,
    .srcwidth = DMA_CTR_WORD_WIDTH,
    .src_burst_size = 0, /* < must 0 */
    .read_num_en = 0,
    .priority = 0,
    .write_num_en = 0,
    .auto_en = 0, /* < must 0 */
};
dma_config_t analog_rx_dma_config = {
    .dst_req_sel = 0,  // tx req
    .src_req_sel = DMA_REQ_ALGM_RX,
    .dst_addr_ctrl = DMA_ADDR_INCREMENT,
    .src_addr_ctrl = DMA_ADDR_FIX,
    .dstmode = DMA_NORMAL_MODE,
    .srcmode = DMA_HANDSHAKE_MODE,
    .dstwidth = DMA_CTR_WORD_WIDTH, /* < must word */
    .srcwidth = DMA_CTR_WORD_WIDTH, /* < must word */
    .src_burst_size = 0,
    .read_num_en = 0,
    .priority = 0,
    .write_num_en = 0,
    .auto_en = 0,  // must 0
};
/**********************************************************************************************************************
 *                                              local variable                                                     *
 *********************************************************************************************************************/
/**********************************************************************************************************************
 *                                          local function prototype                                               *
 *********************************************************************************************************************/

/**
 * @brief      This function serves to judge whether analog write/read is busy .
 * @return     none.
 */
static inline void analog_wait(void);
/**********************************************************************************************************************
 *                                         global function implementation                                             *
 *********************************************************************************************************************/

/**
 * @brief      This function serves to analog register read by byte.
 * @param[in]  addr - address need to be read.
 * @return     the result of read.
 */
unsigned char analog_read_reg8(unsigned char addr)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_ana_len = 0x1;
    reg_ana_ctrl = FLD_ANA_CYC;
    analog_wait();
    unsigned char data = reg_ana_data(0);
    core_restore_interrupt(r);
    return data;
}

/**
 * @brief      This function serves to analog register write by byte.
 * @param[in]  addr - address need to be write.
 * @param[in]  data - the value need to be write.
 * @return     none.
 */
void analog_write_reg8(unsigned char addr, unsigned char data)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_ana_data(0) = data;
    reg_ana_ctrl = (FLD_ANA_CYC | FLD_ANA_RW);
    analog_wait();
    reg_ana_ctrl = 0x00;
    core_restore_interrupt(r);
}

/**
 * @brief      This function serves to analog register write by halfword.
 * @param[in]  addr - address need to be write.
 * @param[in]  data - the value need to be write.
 * @return     none.
 */
void analog_write_reg16(unsigned char addr, unsigned short data)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_ana_addr_data16 = data;
    reg_ana_ctrl = (FLD_ANA_CYC | FLD_ANA_RW);
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function serves to analog register read by halfword.
 * @param[in]  addr - address need to be read.
 * @return     the result of read.
 */
unsigned short analog_read_reg16(unsigned char addr)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_len = 2;
    reg_ana_addr = addr;
    reg_ana_ctrl = FLD_ANA_CYC;
    analog_wait();
    unsigned short data = reg_ana_addr_data16;
    core_restore_interrupt(r);
    return data;
}

/**
 * @brief      This function serves to analog register read by word.
 * @param[in]  addr - address need to be read.
 * @return     the result of read.
 */
unsigned int analog_read_reg32(unsigned char addr)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_len = 4;
    reg_ana_addr = addr;
    reg_ana_ctrl = FLD_ANA_CYC;
    analog_wait();
    unsigned int data = reg_ana_addr_data32;
    core_restore_interrupt(r);
    return data;
}

/**
 * @brief      This function serves to analog register write by word.
 * @param[in]  addr - address need to be write.
 * @param[in]  data - the value need to be write.
 * @return     none.
 */
void analog_write_reg32(unsigned char addr, unsigned int data)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_ana_addr_data32 = data;
    reg_ana_ctrl = (FLD_ANA_CYC | FLD_ANA_RW);
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function serves to analog register write by word using dma.
 * @param[in]  chn - the dma channel.
 * @param[in]  addr - address need to be write.
 * @param[in]  pdat - the value need to be write.
 * @return     none.
 */
void analog_read_reg32_dma(dma_chn_e chn, unsigned char addr, void *pdat)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_len = 0x04;
    reg_ana_addr = addr;
    reg_ana_ctrl = FLD_ANA_CYC;
    reg_dma_src_addr(chn) = 0x80140184;
    reg_dma_dst_addr(chn) = convert_ram_addr_cpu2bus(pdat);
    dma_set_size(chn, 4, DMA_WORD_WIDTH);
    analog_rx_dma_config.dstwidth = DMA_CTR_WORD_WIDTH;
    analog_rx_dma_config.srcwidth = DMA_CTR_WORD_WIDTH;
    dma_config(chn, &analog_rx_dma_config);
    dma_chn_en(chn);
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function serves to analog register write by word using dma.
 * @param[in]  chn - the dma channel.
 * @param[in]  addr - address need to be write.
 * @param[in]  data - the value need to be write.
 * @return     none.
 */
void analog_write_reg32_dma(dma_chn_e chn, unsigned char addr, void *pdat)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_dma_src_addr(chn) = convert_ram_addr_cpu2bus(pdat);
    reg_dma_dst_addr(chn) = 0x80140184;
    dma_set_size(chn, 4, DMA_WORD_WIDTH);
    analog_tx_dma_config.dstwidth = DMA_CTR_WORD_WIDTH;
    analog_tx_dma_config.srcwidth = DMA_CTR_WORD_WIDTH;
    dma_config(chn, &analog_tx_dma_config);
    dma_chn_en(chn);
    reg_ana_ctrl = FLD_ANA_CYC | FLD_ANA_RW;
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function write buffer to analog register.
 * @param[in]  addr - address need to be write.
 * @param[in]  *buff - the buffer need to be write.
 * @param[in]  len - the length of buffer.
 * @return     none.
 */
_attribute_ram_code_sec_noinline_ void analog_write_buff(unsigned char addr, unsigned char *buff, int len)
{
    unsigned char wr_idx = 0;
    unsigned char len_t = len;
    unsigned int r = core_interrupt_disable();
    reg_ana_len = len;
    reg_ana_addr = addr;

    if (len_t <= 4) {
        while (len_t--) {
            reg_ana_data(wr_idx++) = *(buff++);
        }

        reg_ana_ctrl = FLD_ANA_CYC | FLD_ANA_RW;
    } else {
        len_t = 4;
        while (len_t--) {
            reg_ana_data(wr_idx++) = *(buff++);
        }

        reg_ana_ctrl = FLD_ANA_CYC | FLD_ANA_RW;
        len_t = len - 4;
        wr_idx = 0;
        while (len_t--) {
            reg_ana_data(wr_idx++) = *(buff++);
            if (wr_idx == 4) {
                wr_idx = 0;
                while ((reg_ana_irq_sta & FLD_ANA_TXBUFF_IRQ) == 0) {
                }
                // tx_buf_irq
            }
        }
    }
    analog_wait();  // busy
    reg_ana_ctrl = 0x00;
    core_restore_interrupt(r);
}

/**
 * @brief      This function read data from analog register to buffer.
 * @param[in]  addr - address need to be read from.
 * @param[in]  *buff - the buffer need to be put data.
 * @param[in]  len - the length of read data.
 * @return     none.
 */
_attribute_ram_code_sec_noinline_ void analog_read_buff(unsigned char addr, unsigned char *buff, int len)
{
    unsigned int r = core_interrupt_disable();
    unsigned char rd_idx = 0;
    unsigned char len_t = len;
    reg_ana_len = len;
    reg_ana_addr = addr;
    reg_ana_ctrl = FLD_ANA_CYC;
    if (len_t > 4) {
        while ((reg_ana_irq_sta & FLD_ANA_RXBUFF_IRQ) == 0) {
        }
        // rx_buf_irq
        while (len_t--) {
            (*buff++) = reg_ana_data(rd_idx++);
            if (rd_idx == 4) {
                rd_idx = 0;
                if (len_t <= 4) {
                    break;
                } else {
                    while ((reg_ana_irq_sta & FLD_ANA_RXBUFF_IRQ) == 0) {
                    }
                }
                // rx_buf_irq
            }
        }
    }
    analog_wait();
    while (len_t--) {
        (*buff++) = reg_ana_data(rd_idx++);
    }

    reg_ana_ctrl = 0x00;
    core_restore_interrupt(r);
}

/**
 * @brief      This function write buffer to analog register by dma channel.
 * @param[in]  chn - the dma channel.
 * @param[in]  addr - address need to be write.
 * @param[in]  *pdat - the buffer need to be write.
 * @param[in]  len - the length of buffer.
 * @return     none.
 */
void analog_write_buff_dma(dma_chn_e chn, unsigned char addr, unsigned char *pdat, unsigned int len)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_addr = addr;
    reg_dma_src_addr(chn) = convert_ram_addr_cpu2bus(pdat);
    reg_dma_dst_addr(chn) = 0x80140184;
    dma_set_size(chn, len, DMA_WORD_WIDTH);
    analog_tx_dma_config.dstwidth = DMA_CTR_WORD_WIDTH;
    analog_tx_dma_config.srcwidth = DMA_CTR_WORD_WIDTH;
    dma_config(chn, &analog_tx_dma_config);
    dma_chn_en(chn);
    reg_ana_ctrl = 0x60;
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function write buffer to analog register by dma channel.
 * @param[in]  chn - the dma channel.
 * @param[in]  addr - address need to be read from.
 * @param[in]  *pdat - the buffer need to be put data.
 * 			   note: The size of array pdat must be a multiple of 4.
 * 			  	 	For example, if you just need read 5 byte by dma, you should
 * 			  	 	define the size of array pdat to be greater than 8 other than 5.
 * 			  	 	Because the dma would return 4 byte data every time, 5 byte is
 * 			  	 	not enough to store them.
 * @param[in]  len - the length of read data.
 * @return     none.
 */
void analog_read_buff_dma(dma_chn_e chn, unsigned char addr, unsigned char *pdat, unsigned int len)
{
    unsigned int r = core_interrupt_disable();
    reg_ana_len = len;
    reg_ana_addr = addr;

    reg_dma_src_addr(chn) = 0x80140184;
    reg_dma_dst_addr(chn) = convert_ram_addr_cpu2bus(pdat);
    dma_set_size(chn, len, DMA_WORD_WIDTH);
    analog_rx_dma_config.dstwidth = DMA_CTR_WORD_WIDTH;
    analog_rx_dma_config.srcwidth = DMA_CTR_WORD_WIDTH;
    dma_config(chn, &analog_rx_dma_config);
    dma_chn_en(chn);
    reg_ana_ctrl = FLD_ANA_CYC;
    analog_wait();
    core_restore_interrupt(r);
}

/**
 * @brief      This function write buffer to analog register by dma channel.
 * @param[in]  chn  - the dma channel.
 * @param[in]  pdat - the buffer(addr & data) ptr need to be write,
 * 			   note: The array pdat should look like this,
 * 			   |  pdat     |            |        |
 * 			   |  :------  | :----------|  :---- |
 * 			   |  pdat[0]  |   address  |  0x3a  |
 * 			   |  pdat[1]  |    data    |  0x11  |
 * 			   |  pdat[2]  |   address  |  0x3b  |
 *			   |  pdat[3]  |    data    |  0x22  |
 *			   |  ......   |            |        |
 * 				It means write data 0x11 to address 0x3a,
 * 						 write data 0x22 to address 0x3b,
 * 						 ......
 * @param[in]  len - the length of read data.
 * @return     none.
 */
void analog_write_addr_data_dma(dma_chn_e chn, void *pdat, int len)
{
    unsigned int r = core_interrupt_disable();

    reg_dma_src_addr(chn) = convert_ram_addr_cpu2bus(pdat);
    reg_dma_dst_addr(chn) = 0x80140184;
    dma_set_size(chn, len, DMA_WORD_WIDTH);
    analog_tx_dma_config.dstwidth = DMA_CTR_WORD_WIDTH;
    analog_tx_dma_config.srcwidth = DMA_CTR_WORD_WIDTH;
    dma_config(chn, &analog_tx_dma_config);
    dma_chn_en(chn);
    delay_us(1);
    reg_ana_ctrl = FLD_ANA_RW;
    reg_ana_dma_ctl = FLD_ANA_CYC1 | FLD_ANA_DMA_EN;
    delay_us(1);
    while (!(reg_ana_sta & BIT(3))) {
    }

    reg_ana_ctrl = 0x00;
    reg_ana_dma_ctl = 0;
    core_restore_interrupt(r);
}

/**********************************************************************************************************************
  *                    						local function implementation                                             *
  *********************************************************************************************************************/
/**
 * @brief      This function serves to judge whether analog write/read is busy .
 * @return     none.
 */
static inline void analog_wait(void)
{
    while (reg_ana_ctrl & FLD_ANA_BUSY) {
    }
}
